有了前面幾篇的解釋,相信大家已經對 Signal 和 Fine-grained Reactivity 的概念有初步的認識,今天我們就回到開篇內容的主軸,接續理解 Signal 的運作原理,包含 Effect, Computed 所扮演的角色吧!
UI = f(state) 的真正實踐在 fine-grained reactivity 裡,state 被拆分成許多最小不可分的 Signal。
任何由這些來源計算出的值都是 Computed,而所有「和 DOM / 網路 / console 等外界互動」則封裝進 Effect。
還記得前面的提問: 「什麼是 Signal?」的具體描述嗎?
你可以把 Signal 想成一個「盒子」或「容器」,它裡面裝著一個值。這個盒子有幾個關鍵特性:
- 讀取值:你可以隨時讀取盒子裡面的值。
- 修改值:如果 Signal 是可寫的,你可以修改盒子裡面的值。
- 自動通知依賴者:最重要的是,當盒子裡面的值改變時,所有依賴這個 Signal 的部分(例如,你的 UI 元素、其他計算屬性等)都會被自動且高效地通知,然後它們會根據新的值進行更新。
依照前面描述你的 signal 應該要有下面圖表的特性:
observers
observers
observers
標為 dirty
流程大概會如下圖所示:
用來調度處理我們一開始的核心流程,把所有被 Signal 標記為髒污的 Computed 與 Effect 先暫存到佇列,再在下一個非同步時機一次性依順序批次執行:
setter
只「標記」髒污 → 非同步交由 Scheduler 處理queueMicrotask
或 requestAnimationFrame
形成批次這個在框架中通常不會開放給一般開發者做調整,而且會依照不同框架採用的渲染手段而有差異,因為單純調度資料而不順帶調整渲染,會造成不必要的額外開銷。如果有,通常會做成 Adapter 的方式,讓開發者使用。
情境 | 問題 | 解法 |
---|---|---|
連環寫入 | Effect 裡多次 set() → 重算過多 |
用 batch() 或 setter 內合併 |
無限循環 | Effect 改寫自己依賴的 Signal | Dev-mode 偵測 & 報錯 |
依賴環 | Computed A 依賴 B,B 又依賴 A | 建圖時偵測 cycle |
記憶體洩漏 | 動態建立 Effect 卻沒 dispose | 在 unmount 時自動清理(cleanup function) |
希望透過上面的圖表能夠讓大家淺顯易懂一點,這篇的重點大概綜整如下:
如果能把這三大角色與一個排程器拼在一起,就是 Fine-grained Reactivity「即寫即用、最小更新」的關鍵。
下一篇,我們來理解如何透過 Dependency Tracking(依賴追蹤)的方法,將這三個角色彼此緊密連接並保持運作順暢。